﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using EnvDTE;
using EnvDTE80;
using AutoUnitTest.Common;

namespace GUAPAddin.Common
{
    public class AnalyzeProcess
    {
        //public List<String> funcName = new List<String>();
       // public List<String> funcBody = new List<String>();
        private int _idVertex = 1, _idEdge = 1;
        private int _currentClassNumber;

        private int NewVertexId
        {
            get
            {
                return _idVertex++;
            }
        }

        private int NewEdgeId
        {
            get
            {
                return _idEdge++;
            }
        }

        private class Param
        {
            public String Name { get; private set; }
            public String Type { get; private set; }

            public Param(string name, string type)
            {
                Name = name;
                Type = type;
            }
        }


        private List<Vertex> vertexTempList = new List<Vertex>();
        private List<Edge> edgeTempList = new List<Edge>();

        public Graph getGraph()
        {
            return new Graph(vertexTempList, edgeTempList);
        }
        
        public void StartAnalyze(DTE2 dte)
        {
            Solution2 solution = dte.Solution as Solution2;
            if (solution != null)
            {
                ProcessSolution(solution);
            }
        }

        private void ProcessSolution(Solution2 solution)
        {
            foreach (Project project in solution.Projects)
            {
                ProcessProject(project);
            }
        }

        private void ProcessProject(Project project)
        {
            foreach (ProjectItem item in project.ProjectItems)
            {
                ProcessProjectItem(item);
            }
        }
        
        private void ProcessProjectItem(ProjectItem item)
        {
            FileCodeModel2 model = item.FileCodeModel as FileCodeModel2;
            if(item.FileCount > 0 && model == null)
            {
                //Means it is FOLDER
                foreach(ProjectItem projectItem in item.ProjectItems)
                {
                    _currentClassNumber = -1;
                    ProcessProjectItem(projectItem);
                }
            }
            else if(model != null)
            {
                foreach(CodeElement2 ce in model.CodeElements)
                {
                    ProcessCodeElement(ce);
                }
            }
        }

        private void ProcessCodeElement(CodeElement2 elem)
        {
            switch (elem.Kind)
            {
                case vsCMElement.vsCMElementNamespace:
                    NamespaceCode((CodeNamespace)elem);
                    break;
                case vsCMElement.vsCMElementClass:
                    ClassCode((CodeClass2)elem);
                    break;
                case vsCMElement.vsCMElementFunction:
                    vertexTempList.Add(new Vertex(new VertexType("function"),
                        ((CodeFunction2)elem).FullName, NewVertexId));
                    FunctionCode((CodeFunction2)elem);
                    break;
                case vsCMElement.vsCMElementStruct:
                    vertexTempList.Add(new Vertex(new VertexType("struct"), 
                        ((CodeStruct2)elem).FullName, NewVertexId));
                    StructCode((CodeStruct2)elem);
                    break;
                case vsCMElement.vsCMElementVariable:
                    AddClassVariable(new Param(((CodeVariable2)elem).FullName, 
                        ((CodeVariable2)elem).Type.AsString));
                    break;
            }
        }

        private void NamespaceCode(CodeNamespace cn)
        {
            foreach (CodeElement2 ce in cn.Children)
            {
                ProcessCodeElement(ce);
            }
        }

        private void ClassCode(CodeClass2 cc)
        {
            _currentClassNumber = AddClass(cc.FullName, 0);
            if (cc.Bases.Count > 0)
            {
                int classNum = vertexTempList.Count - 1; // запоминаем номер только что добавленного класса
                foreach (CodeElement2 elem in cc.Bases)
                {
                    AddClass(elem.FullName, classNum);
                }
            }

            foreach (CodeElement2 ce in cc.Children)
            {
                ProcessCodeElement(ce);
            }
        }

        private void StructCode(CodeStruct2 cs)
        {
            string name;

            ArrayList structItems = new ArrayList();

            name = cs.Name;

            foreach (CodeElement ce in cs.Children)
            {
                if (ce.Kind == vsCMElement.vsCMElementVariable)
                {
                    CodeVariable2 cv = (CodeVariable2)ce;
                }
            }
        }

        private void FunctionCode(CodeFunction2 cf)
        {
            EditPoint2 startPoint;
            EditPoint2 endPoint;
            String textBody;

            //startPoint = cf.GetStartPoint(vsCMPart.vsCMPartHeader);
            //  startPoint = (EditPoint2)cf.GetStartPoint(vsCMPart.vsCMPartHeader).CreateEditPoint();
            //    endPoint = (EditPoint2)cf.GetEndPoint(vsCMPart.vsCMPartHeader).CreateEditPoint();
            //      textBody = startPoint.GetText(endPoint);

            List<Param> funcParams = new List<Param>();
            foreach (CodeElement ce in cf.Children)
            {
                if (ce.Kind == vsCMElement.vsCMElementParameter)
                {
                    var p = (CodeParameter2) ce;
                    //log.Add("\t\t Function Param " + paramCount + ": " + pram.Type.AsString);
                   
                    funcParams.Add(new Param(p.Name, p.Type.CodeType.FullName));
                }
            }


            string returnType = cf.Type.CodeType.FullName;

            string accessType = "";
            switch (cf.Access)
            {
                case vsCMAccess.vsCMAccessPublic:
                    accessType = "public";
                    break;
                case vsCMAccess.vsCMAccessPrivate:
                    accessType = "private";
                    break;
                case vsCMAccess.vsCMAccessProtected:
                    accessType = "protected";
                    break;
                case vsCMAccess.vsCMAccessProject:
                    accessType = "internal";
                    break;
                default:
                    accessType = "";
                    break;
            }

            AddFunction(cf.Name, returnType, accessType, funcParams);

        }

        private int AddClass(String name, int inheritanceClassNumber)
        {
            
            if (inheritanceClassNumber == 0)
            {
                vertexTempList.Add(new Vertex(new VertexType("class"), name, NewVertexId));
                return vertexTempList.Count - 1;
            }
            // наследование (добавляем базовый класс)
            //добавляем базовый классы
            vertexTempList.Add(new Vertex(new VertexType("class"), name, NewVertexId));
            //добавляем дугу от базового класса к производному
            edgeTempList.Add(new Edge(new EdgeType("t"), NewEdgeId, vertexTempList[vertexTempList.Count - 1], vertexTempList[inheritanceClassNumber]));
            return 0;
        }

        private void AddFunction(string name, string type, string visibility, List<Param> funcParams)
        {
            int functionNumber = vertexTempList.Count - 1; // номер добавленной функции (чтобы лишний раз не вычислять)
          //  vertexTempList.Add(new Vertex(new VertexType("function"), name, NewVertexId));
            vertexTempList.Add(new Vertex(new VertexType("type"), type, NewVertexId));
            // связывем вершину типа с вершиной ф-ии
            edgeTempList.Add(new Edge(new EdgeType("t"), NewEdgeId, vertexTempList[vertexTempList.Count - 1], 
                vertexTempList[functionNumber]));

            vertexTempList.Add(new Vertex(new VertexType("L"), visibility, NewVertexId));
            // связывем вершину видимости ф-ии с вершиной ф-ии
            edgeTempList.Add(new Edge(new EdgeType("L"), NewEdgeId, vertexTempList[vertexTempList.Count - 1],
                vertexTempList[functionNumber]));

            // если ф-ия принадлежит классу - устанавливаем связь с этим классом
            if (_currentClassNumber > -1)
            {
                edgeTempList.Add(new Edge(new EdgeType("m"), NewEdgeId, vertexTempList[functionNumber], 
                    vertexTempList[_currentClassNumber]));
            }

            // если есть параметры, добавляем их
            if (funcParams.Count != 0)
            {
                foreach (Param funcParam in funcParams)
                {
                    vertexTempList.Add(new Vertex(new VertexType("field"), funcParam.Name, NewVertexId));
                    vertexTempList.Add(new Vertex(new VertexType("type"), funcParam.Type, NewVertexId));

                    // связываем вершину параметра ф-ии с её же типом
                    edgeTempList.Add(new Edge(new EdgeType("t"), NewEdgeId, 
                        vertexTempList[vertexTempList.Count - 1], vertexTempList[vertexTempList.Count - 2]));

                    // связываем параметр ф-ии с самой функцией
                    edgeTempList.Add(new Edge(new EdgeType("p"), NewEdgeId, 
                        vertexTempList[vertexTempList.Count - 2], vertexTempList[functionNumber]));
                }
            }
        }

        private void AddClassVariable(Param classParam)
        {
            vertexTempList.Add(new Vertex(new VertexType("field"), classParam.Name, NewVertexId));
            vertexTempList.Add(new Vertex(new VertexType("type"), classParam.Type, NewVertexId));
            edgeTempList.Add(new Edge(new EdgeType("t"), NewEdgeId, vertexTempList[vertexTempList.Count - 1],
                vertexTempList[vertexTempList.Count - 2]));

            if (_currentClassNumber > -1)
                edgeTempList.Add(new Edge(new EdgeType("f"), NewEdgeId, vertexTempList[vertexTempList.Count - 2], 
                    vertexTempList[_currentClassNumber]));
        }
    }
}